package com.poly.flowable.service.impl;

import cn.hutool.core.map.MapUtil;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.poly.common.core.constant.Constants;
import com.poly.common.core.constant.DictConstants;
import com.poly.common.core.constant.HttpStatus;
import com.poly.common.core.exception.CustomException;
import com.poly.common.core.tools.utils.DateUtil;
import com.poly.common.core.tools.utils.Func;
import com.poly.common.core.utils.AssertUtil;
import com.poly.common.core.utils.IdUtil;
import com.poly.common.core.utils.listener.ListenerUtils;
import com.poly.common.core.web.page.TableData;
import com.poly.common.security.utils.DictUtils;
import com.poly.common.security.utils.SecurityUtils;
import com.poly.flowable.cache.FormCache;
import com.poly.flowable.domain.SysForm;
import com.poly.flowable.enums.form.DataTypeEnum;
import com.poly.flowable.enums.form.QueryTypeEnum;
import com.poly.flowable.enums.form.SceneGroupEnum;
import com.poly.flowable.enums.form.SystemFieldEnum;
import com.poly.flowable.mapper.SysFormBizMapper;
import com.poly.flowable.pojo.dto.FormDTO;
import com.poly.flowable.pojo.dto.FormFieldDTO;
import com.poly.flowable.pojo.dto.FormModelDTO;
import com.poly.flowable.pojo.dto.OrderBy;
import com.poly.flowable.pojo.query.FormQuery;
import com.poly.flowable.service.IFlowDefinitionService;
import com.poly.flowable.service.ISysFormBizService;
import com.poly.flowable.service.ISysFormService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.*;
import java.util.stream.Collectors;

/**
 * 流程表单Service业务层处理
 *
 * @author Yang huijing
 * @date 2021-04-03
 */
@Service
public class SysFormBizServiceImpl implements ISysFormBizService {

    @Autowired
    private ISysFormService formService;

    @Autowired
    private SysFormBizMapper formBizMapper;

    @Autowired
    private IFlowDefinitionService flowDefinitionService;

    public synchronized Object getValue(FormFieldDTO fa, Object value) {
        AssertUtil.isFalse(fa.getRequired() && Func.isEmpty(value), fa.getComment() + "不允许为空！");
//        AssertUtil.isFalse(Func.isNotEmpty(fa.getRegex())
//                        && !Pattern.matches(fa.getRegex(), value.toString()),
//                fa.getComment() + "格式不正确！");
        Object val = null;
        if (value instanceof JSONArray && (fa.getControlType().equals("file-upload") || fa.getControlType().equals("picture-upload"))) {
            JSONArray array = (JSONArray) value;
            List<String> filenameList = new ArrayList<>();
            for (int i = 0; i < array.size(); i++) {
                if (array.getJSONObject(i).containsKey("response")) {
                    String filename = array.getJSONObject(i).getJSONObject("response").getJSONObject("data").getString("name");
                    filenameList.add(filename);
                } else {
                    filenameList.add(array.getJSONObject(i).getString("filename"));
                }
            }
            val = Func.join(filenameList, ",");
        } else if (value instanceof JSONArray && (fa.getControlType().equals("checkbox") || fa.getControlType().equals("select"))) {
            JSONArray array = (JSONArray) value;
            val = Func.join(array.toJavaList(String.class), ",");
        } else {
            val = value;
        }
        AssertUtil.isFalse(DataTypeEnum.VARCHAR.name().equals(fa.getDataType())
                        && Func.isNotEmpty(val)
                        && val.toString().length() > fa.getLen(),
                fa.getComment() + "长度不能超" + fa.getLen() + "字符！");
        if (val == null && Func.isNotEmpty(fa.getDefaultValue())) {
            val = fa.getDefaultValue();
        }
        return val;
    }


    public synchronized Map<String, Object> getFormData(List<FormFieldDTO> formFieldList, FormDTO dto) {
        Map<String, Object> dataMap = new HashMap<>();
        if (Func.isNotEmpty(formFieldList)) {
            for (FormFieldDTO item : formFieldList) {
                try{
                    Object value = getValue(item, dto.getData().get(item.getFieldName()));
                    if (value == null) {
                        continue;
                    }
                    if (DataTypeEnum.DATETIME.name().equals(item.getDataType())) {
                        dataMap.put(item.getFieldName(), DateUtil.format(DateUtil.parse(value.toString()), "yyyy-MM-dd HH:mm:ss"));
                    } else if (DataTypeEnum.DATE.name().equals(item.getDataType())) {
                        dataMap.put(item.getFieldName(), DateUtil.format(DateUtil.parse(value.toString()), "yyyy-MM-dd"));
                    } else {
                        dataMap.put(item.getFieldName(), value);
                    }
                }catch (Exception e){
                    throw new CustomException("["+item.getFieldName()+"]非法参数："+JSON.toJSONString(dto.getData().get(item.getFieldName())), HttpStatus.BAD_REQUEST);
                }
            }
        }
        return dataMap;
    }

    @Override
    public synchronized Long add(Long formId, FormDTO dto) {
        AssertUtil.isTrue(Func.isNotEmpty(dto.getData()), "数据参数错误");
        SysForm form = FormCache.getForm(formId);
        AssertUtil.isNotNull(form, "参数错误");
        List<FormFieldDTO> formFieldList = formService.getSceneFieldList(form, SceneGroupEnum.ADD);
        if (Func.isNotEmpty(formFieldList)) {
            Map<String, Object> dataMap = getFormData(formFieldList, dto);
            Long id = IdUtil.getId();
            Integer count = formBizMapper.add(Constants.FORM_PREFIX.concat(form.getFormName()), id, SecurityUtils.getUserId(), dataMap);
            if (count > 0) {
                return id;
            }
        }
        return null;
    }

    @Override
    public synchronized Long update(Long formId, FormDTO dto) {
        AssertUtil.isTrue(Func.isNotEmpty(dto.getData()), "数据参数错误");
        SysForm form = FormCache.getForm(formId);
        AssertUtil.isNotNull(form, "参数错误");
        AssertUtil.isNotNull(dto.getId(), "参数错误");
        List<FormFieldDTO> formFieldList = formService.getSceneFieldList(form, SceneGroupEnum.EDIT);
        if (Func.isNotEmpty(formFieldList)) {
            Map<String, Object> dataMap = getFormData(formFieldList, dto);
            Integer count = formBizMapper.update(Constants.FORM_PREFIX.concat(form.getFormName()), dto.getId(), SecurityUtils.getUserId(), dataMap);
            if (count > 0) {
                return dto.getId();
            }
        }
        return null;
    }

    @Override
    public synchronized Integer remove(Long formId, List<Long> idList) {
        SysForm form = FormCache.getForm(formId);
        AssertUtil.isNotNull(form, "参数错误");
        return formBizMapper.remove(Constants.FORM_PREFIX.concat(form.getFormName()), idList, SecurityUtils.getUserId());
    }


    public synchronized List<Map<String, Object>> getList(String formName, List<FormFieldDTO> showFieldList, List<FormFieldDTO> queryFieldList,
                                                          Map<String, Object> params, List<OrderBy> orderByList, Integer offset, Integer pageSize) {
        return formBizMapper.getList(Constants.FORM_PREFIX.concat(formName), showFieldList, queryFieldList, params, orderByList, offset, pageSize);
    }

    public synchronized List<Map<String, Object>> getList(String formName, List<FormFieldDTO> showFieldList, List<FormFieldDTO> queryFieldList,
                                                          Map<String, Object> params, List<OrderBy> orderByList) {
        return getList(formName, showFieldList, queryFieldList, params, orderByList, null, null);
    }

    public synchronized List<Map<String, Object>> getList(String formName, List<FormFieldDTO> showFieldList, List<FormFieldDTO> queryFieldList, Map<String, Object> params) {
        return getList(formName, showFieldList, queryFieldList, params, null);
    }

    public synchronized Map<String, Object> getOne(String formName, List<FormFieldDTO> showFieldList, List<FormFieldDTO> queryFieldList, Map<String, Object> params) {
        List<Map<String, Object>> dataList = getList(formName, showFieldList, queryFieldList, params, null, 0, 1);
        if (Func.isNotEmpty(dataList)) {
            return dataList.get(0);
        }
        return null;
    }

    /**
     * 构建场景字段数据
     *
     * @param src               --数据源
     * @param sceneFieldList    --场景字段集
     * @param listenerFieldList --监听字段集
     * @return
     */
    private synchronized Map<String, Object> constructionSceneData(Map<String, Object> src, List<FormFieldDTO> sceneFieldList, List<FormFieldDTO> listenerFieldList) {
        if (Func.isEmpty(src)) {
            return null;
        }
        //设置监听字段数据
        if (Func.isNotEmpty(listenerFieldList)) {
            listenerFieldList.forEach(p -> {
                if (src.containsKey(p.getListenerFieldName())) {
                    String listener = "";
                    if (p.getListener().indexOf(".") > -1) {
                        listener = p.getListener();
                    } else {
                        listener = "com.poly.flowable.listener." + p.getListener() + "#handle";
                    }
                    src.put(p.getFieldName(), ListenerUtils.invoke(listener, src.get(p.getListenerFieldName())).orElse(""));
                } else {
                    src.put(p.getFieldName(), "");
                }
            });
        }
        Map<String, Object> map = new HashMap<>();
        if (Func.isNotEmpty(sceneFieldList)) {
            //去除非场景字段
            sceneFieldList.forEach(p -> {
                if ((Func.isNotEmpty(p.getMultiple()) && p.getMultiple()) || "checkbox".equals(p.getControlType())) {
                    map.put(p.getFieldName(), MapUtil.getStr(src, p.getFieldName(), "").split("\\,"));
                } else {
                    map.put(p.getFieldName(), src.get(p.getFieldName()));
                }
                if (Func.isNotEmpty(p.getListenerFieldName()) && !map.containsKey(p.getListenerFieldName())) {
                    map.put(p.getListenerFieldName(), src.get(p.getListenerFieldName()));
                }
            });
        }
        return map;
    }

    /**
     * 构建场景字段数据
     *
     * @param src            --数据源
     * @param sceneFieldList --场景字段集
     * @return
     */
    private synchronized Map<String, Object> constructionSceneData(Map<String, Object> src, List<FormFieldDTO> sceneFieldList) {
        List<FormFieldDTO> listenerFieldList = sceneFieldList.stream().filter(p -> Func.isNotEmpty(p.getListener()) && Func.isNotEmpty(p.getListenerFieldName())).collect(Collectors.toList());
        return constructionSceneData(src, sceneFieldList, listenerFieldList);
    }

    @Override
    public synchronized TableData<Map<String, Object>> queryPage(Long formId, FormQuery query) {
        SysForm form = FormCache.getForm(formId);
        AssertUtil.isNotNull(form, "参数错误");
        if (Func.isEmpty(query.getSceneGroup())) {
            query.setSceneGroup(SceneGroupEnum.LIST.name());
        }
        List<FormFieldDTO> allField = formService.getFormField(form.getFormContent());
        List<FormFieldDTO> queryField = formService.getQueryFieldList(allField);
        List<Map<String, Object>> list = new ArrayList<>();
        Long total = formBizMapper.getCount(Constants.FORM_PREFIX.concat(form.getFormName()), queryField, query.getParams());
        if (total > 0) {
            List<FormFieldDTO> showField = formService.getSceneFieldList(allField, SceneGroupEnum.valueOf(query.getSceneGroup()));
            setSystemField(showField, query.getSceneGroup(), SystemFieldEnum.ID, SystemFieldEnum.PROC_DEPLOY_ID, SystemFieldEnum.PROC_DEF_ID, SystemFieldEnum.PROC_INST_ID, SystemFieldEnum.CREATE_BY, SystemFieldEnum.CREATE_TIME, SystemFieldEnum.UPDATE_BY, SystemFieldEnum.UPDATE_TIME);
            List<Map<String, Object>> dataList = getList(form.getFormName(), null, queryField, query.getParams(), Arrays.asList(new OrderBy("create_time", 2)), query.getOffset(), query.getPageSize());
            if (Func.isNotEmpty(dataList)) {
                List<FormFieldDTO> listenerFieldList = showField.stream().filter(p -> Func.isNotEmpty(p.getListener()) && Func.isNotEmpty(p.getListenerFieldName())).collect(Collectors.toList());
                List<FormFieldDTO> finalShowField = showField;
                dataList.forEach(o -> list.add(constructionSceneData(o, finalShowField, listenerFieldList)));
                dataList = null;
            }
            showField = null;
        }
        queryField = null;
        allField = null;
        return TableData.data(list, total);
    }

    public synchronized void setSystemField(List<FormFieldDTO> formFieldList, String sceneGroup, SystemFieldEnum... fields) {
        setSystemField(formFieldList, new String[]{sceneGroup}, fields);
    }

    public synchronized void setSystemField(List<FormFieldDTO> formFieldList, String[] sceneGroups, SystemFieldEnum... fields) {
        if (Func.isNotEmpty(fields)) {
            for (SystemFieldEnum field : fields) {
                setSystemField(formFieldList, field, sceneGroups);
            }
        }
    }

    public synchronized void setSystemField(List<FormFieldDTO> formFieldList, SystemFieldEnum field, String... sceneGroups) {
        if (Func.isNotEmpty(formFieldList)) {
            formFieldList.add(new FormFieldDTO(field, sceneGroups));
        }
    }

    public synchronized Map<String, Object> get(SysForm form, Long id, List<FormFieldDTO> showFieldList) {
        AssertUtil.isNotNull(form, "参数错误");
        List<FormFieldDTO> queryField = new ArrayList<>();
        queryField.add(new FormFieldDTO(SystemFieldEnum.ID, QueryTypeEnum.EQ));
        Map<String, Object> params = new HashMap<>();
        params.put("id", id);
        return getOne(form.getFormName(), showFieldList, queryField, params);
    }

    public synchronized Map<String, Object> get(SysForm form, Long id) {
        return get(form, id, null);
    }

    @Override
    public synchronized Map<String, Object> get(Long formId, Long id) {
        SysForm form = FormCache.getForm(formId);
        AssertUtil.isNotNull(form, "参数错误");
        List<FormFieldDTO> allField = formService.getFormField(form.getFormContent());
        List<FormFieldDTO> showFieldList = formService.getSceneFieldList(allField, SceneGroupEnum.DETAIL);
        setSystemField(showFieldList, SceneGroupEnum.DETAIL.name(), SystemFieldEnum.ID, SystemFieldEnum.PROC_DEPLOY_ID, SystemFieldEnum.PROC_DEF_ID, SystemFieldEnum.PROC_INST_ID, SystemFieldEnum.CREATE_BY, SystemFieldEnum.CREATE_TIME, SystemFieldEnum.UPDATE_BY, SystemFieldEnum.UPDATE_TIME);
        Map<String, Object> map = get(form, id, null);
        return constructionSceneData(map, showFieldList, null);
    }


    public synchronized Map<String, Object> getDetail(SysForm form, Long id) {
        AssertUtil.isNotNull(form, "参数错误");
        List<FormFieldDTO> allField = formService.getFormField(form.getFormContent());
        List<FormFieldDTO> showFieldList = formService.getSceneFieldList(allField, SceneGroupEnum.DETAIL);
        setSystemField(showFieldList, SceneGroupEnum.DETAIL.name(), SystemFieldEnum.ID, SystemFieldEnum.PROC_DEPLOY_ID, SystemFieldEnum.PROC_DEF_ID, SystemFieldEnum.PROC_INST_ID, SystemFieldEnum.CREATE_BY, SystemFieldEnum.CREATE_TIME, SystemFieldEnum.UPDATE_BY, SystemFieldEnum.UPDATE_TIME);
        Map<String, Object> map = get(form, id, null);
        return constructionSceneData(map, showFieldList);
    }

    @Override
    public synchronized Map<String, Object> getDetail(Long formId, Long id) {
        SysForm form = FormCache.getForm(formId);
        return getDetail(form, id);
    }

    @Override
    public synchronized FormModelDTO getFormConfig(Long formId) {
        SysForm form = FormCache.getForm(formId);
        FormModelDTO model = null;
        if (Func.isNotEmpty(form)) {

            model = new FormModelDTO(form.getFormName(), form.getRemark());
            Map<String, Object> fdMap = flowDefinitionService.getDeployByFormId(formId);
            if (Func.isNotEmpty(fdMap)) {
                model.setDeployId(MapUtil.getStr(fdMap, "deployId", ""));
                model.setProcDefId(MapUtil.getStr(fdMap, "procDefId", ""));
            }
            JSONObject jsonObject = JSON.parseObject(form.getFormContent());
            List<FormFieldDTO> formFieldList = formService.getFormField(jsonObject);
            if (Func.isEmpty(formFieldList)) {
                formFieldList = new ArrayList<>();
            }
            formFieldList.add(0, new FormFieldDTO(SystemFieldEnum.ID, SceneGroupEnum.LIST.name()));
            setSystemField(formFieldList, SceneGroupEnum.LIST.name(), SystemFieldEnum.CREATE_TIME);
            model.setFieldList(formFieldList);
        }
        return model;
    }

    @Override
    public synchronized JSONObject getFormModel(Long formId) {
        SysForm form = FormCache.getForm(formId);
        JSONObject model = null;
        if (Func.isNotEmpty(form)) {
            model = JSON.parseObject(form.getFormContent());
        }
        return model;
    }

    @Override
    @Transactional
    public synchronized Boolean startProcess(Long formId, FormDTO dto) {
        AssertUtil.isTrue(Func.isNotEmpty(dto.getDeployId()) && Func.isNotEmpty(dto.getProcDefId()) && Func.isNotEmpty(dto.getId()), "参数错误");
        SysForm form = FormCache.getForm(formId);
        AssertUtil.isTrue(Func.isNotEmpty(form), "表单不存在");
        Map<String, Object> dtl = get(form, dto.getId());
        AssertUtil.isTrue(Func.isNotEmpty(dtl), "[" + dto.getId() + "]数据不存在");
        String status = MapUtil.getStr(dtl, "status", "");
        AssertUtil.isTrue(status.equals("0"), "状态为" + DictUtils.getDictLabel(DictConstants.APPROVE_STATUS, status) + "，不能发起流程");
        String procInstId = flowDefinitionService.startProcessInstanceById(dto.getProcDefId(), dto.getId().toString(), dto.getData());
        if (Func.isNotEmpty(procInstId)) {
            formBizMapper.startFlow(Constants.FORM_PREFIX.concat(form.getFormName()), dto.getId(), SecurityUtils.getUserId(), dto.getDeployId(), dto.getProcDefId(), procInstId, "1");
            return true;
        }
        return false;
    }

}
